NOTE: This script is for learning purposes only and does not constitute a recommendation for buying or selling any stock mentioned in this script.
SUMMARY: This project aims to construct and test an algorithmic trading model and document the end-to-end steps using a template.
INTRODUCTION: This algorithmic trading model examines a simple mean-reversion strategy for a stock. The model enters a position when the price reaches either the upper or lower Relative Strength Indicator thresholds for the last X number of days. The model will exit the trade when the stock price crosses the upper or the lower RSI line for the same window size.
In iteration Take1, we set up the models using an RSI window size for long trades only. The window size varied from 10 to 50 trading days at a 5-day increment.
In iteration Take2, we set up the models using an RSI window size for long and short trades. The window size varied from 10 to 50 trading days at a 5-day increment.
In iteration Take3, we set up the models using an RSI window size for long trades only. The window size varied from 10 to 50 trading days at a 5-day increment. In addition, we varied the upper and lower RSI thresholds to examine their effects on the return.
In this Take4 iteration, we will set up the models using an RSI window size for long and short trades. The window size will vary from 10 to 50 trading days at a 5-day increment. In addition, we will vary the upper and lower RSI thresholds to examine their effects on the return.
ANALYSIS: In iteration Take1, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 143.51 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
In iteration Take2, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 51.19 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
In this Take3 iteration, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 143.51 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
In this Take4 iteration, we analyzed the stock prices for Costco Wholesale (COST) between January 1, 2016, and April 23, 2021. The top trading model produced a profit of 51.19 dollars per share. The buy-and-hold approach yielded a gain of 211.45 dollars per share.
CONCLUSION: For the stock of COST during the modeling time frame, the long-and-short with variable RSI thresholds trading strategy did not produce a better return than the buy-and-hold approach. We should consider modeling this stock further by experimenting with more variations of the strategy.
Dataset ML Model: Time series analysis with numerical attributes
Dataset Used: Quandl
An algorithmic trading modeling project generally can be broken down into about five major tasks:
# # Install the necessary packages for Colab
# !pip install python-dotenv PyMySQL
# # Retrieve the GPU information from Colab
# gpu_info = !nvidia-smi
# gpu_info = '\n'.join(gpu_info)
# if gpu_info.find('failed') >= 0:
# print('Select the Runtime → "Change runtime type" menu to enable a GPU accelerator, ')
# print('and then re-execute this cell.')
# else:
# print(gpu_info)
# # Retrieve the memory configuration from Colab
# from psutil import virtual_memory
# ram_gb = virtual_memory().total / 1e9
# print('Your runtime has {:.1f} gigabytes of available RAM\n'.format(ram_gb))
#
# if ram_gb < 20:
# print('To enable a high-RAM runtime, select the Runtime → "Change runtime type"')
# print('menu, and then select High-RAM in the Runtime shape dropdown. Then, ')
# print('re-execute this cell.')
# else:
# print('You are using a high-RAM runtime!')
# # Retrieve the CPU information
# ncpu = !nproc
# print("The number of available CPUs is:", ncpu[0])
import pandas as pd
import matplotlib.pyplot as plt
import os
import sys
from datetime import date, datetime, timedelta
import requests
import json
from dotenv import load_dotenv
import statistics as stats
# Begin the timer for the script processing
startTimeScript = datetime.now()
# Specify the key modeling parameters below
STOCK_SYMBOL = 'COST'
INITIAL_CAPITAL = 0
# Specify the moving average parameters for the trading strategy
WINDOW_MIN = 10
WINDOW_MAX = 50
WINDOW_INCREMENT = 5
VOL_MA_MIN = 10
VOL_MA_MAX = 10
VOL_MA_INCREMENT = 10
HOLDING_MIN = 999
HOLDING_MAX = 999
HOLDING_INCREMENT = 10
RSI_HIGH = 70
RSI_MIDDLE = 50
RSI_LOW = 30
RSI_SIZE = 20
RSI_INCREMENT=5
GAIN_MAX = 0.99
LOSS_MAX = 0.99
MEAN_REVERSION = True
TREND_FOLLOWING = False
if MEAN_REVERSION and TREND_FOLLOWING: sys.exit("Cannot have both MEAN_REVERSION and TREND_FOLLOWING flags be set to True. Script Processing Aborted!!!")
LONG_ONLY = False
SHORT_ONLY = False
if LONG_ONLY and SHORT_ONLY: sys.exit("Cannot have both LONG_ONLY and SHORT_LONG flags be set to True. Script Processing Aborted!!!")
# The number of extra days of data we need for calculating moving averages (usually equals to the largest value of slow MA)
EXTRA_DAYS = WINDOW_MAX
# EXTRA_DAYS = SLOW_MA_MAX
MODEL_START_DATE = date(2016, 1, 1)
print("Starting date for the model:", MODEL_START_DATE)
# MODEL_END_DATE = datetime.now().date()
MODEL_END_DATE = date(2021, 4, 23)
print("Ending date for the model:", MODEL_END_DATE)
# data_start_date = MODEL_START_DATE
data_start_date = MODEL_START_DATE - timedelta(days=int(EXTRA_DAYS * 2)) # Need more pricing data to calculate moving averages
print("First date of data we need for modeling:", data_start_date)
data_end_date = MODEL_END_DATE
print("Last date of data we need for modeling:", data_end_date)
Starting date for the model: 2016-01-01 Ending date for the model: 2021-04-23 First date of data we need for modeling: 2015-09-23 Last date of data we need for modeling: 2021-04-23
# Specify the script running parameters below
# Set Pandas options
pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)
pd.set_option("display.width", 384)
# Configure the plotting style
plt.style.use('seaborn')
# Set up the verbose flag to print detailed messages for debugging (setting True will activate!)
verbose_signals = False
verbose_models = False
verbose_graphs = True
verbose_trade_actions = False
verbose_portfolios = False
verbose_transactions = False
verbose_positions = False
# Set up the parent directory location for loading the dotenv files
# Mount Google Drive locally for storing files
# from google.colab import drive
# drive.mount('/content/gdrive')
# gdrivePrefix = '/content/gdrive/My Drive/Colab_Downloads/'
# env_path = '/content/gdrive/My Drive/Colab Notebooks/'
# dotenv_path = env_path + "python_script.env"
# load_dotenv(dotenv_path=dotenv_path)
# Set up access to the dotenv file on local PC
env_path = "/Users/david/PycharmProjects/"
dotenv_path = env_path + "python_script.env"
load_dotenv(dotenv_path=dotenv_path)
True
# Set up the data service provider and data acquisition parameters
data_service = 'Quandl'
# Check and see whether the API key is available
api_key = os.environ.get('QUANDL_API')
if api_key is None: sys.exit("API key for Quandl not available. Script Processing Aborted!!!")
start_date_string = data_start_date.strftime('%Y-%m-%d')
end_date_string = data_end_date.strftime('%Y-%m-%d')
api_url = "https://www.quandl.com/api/v3/datatables/SHARADAR/SEP.json?date.gte=%s&date.lte=%s&ticker=%s&api_key=%s&qopts.data_version=2" % (start_date_string, end_date_string, STOCK_SYMBOL, api_key)
response = requests.get(api_url)
resp_dict = json.loads(response.text)
stock_rawdata = pd.DataFrame(resp_dict['datatable']['data'])
print(len(stock_rawdata), 'data points retrieved from the API call.')
1406 data points retrieved from the API call.
stock_rawdata.columns = ['ticker', 'date', 'open', 'high', 'low', 'close', 'volume', 'closeadj', 'closeunadj', 'lastupdated']
# stock_rawdata.set_index('date', inplace=True)
stock_rawdata.index = pd.to_datetime(stock_rawdata.date)
stock_pricing = stock_rawdata.sort_index(ascending=True)
print(stock_pricing.head())
print()
print(stock_pricing.tail())
ticker date open high low close volume closeadj closeunadj lastupdated
date
2015-09-23 COST 2015-09-23 143.46 145.630 142.65 145.43 2310212.0 128.808 145.43 2021-02-04
2015-09-24 COST 2015-09-24 144.37 145.420 143.65 144.87 1974868.0 128.312 144.87 2021-02-04
2015-09-25 COST 2015-09-25 145.95 146.900 145.01 145.55 1845691.0 128.914 145.55 2021-02-04
2015-09-28 COST 2015-09-28 145.39 145.800 143.29 143.55 2506451.0 127.143 143.55 2021-02-04
2015-09-29 COST 2015-09-29 143.04 144.135 142.25 143.72 2513094.0 127.294 143.72 2021-02-04
ticker date open high low close volume closeadj closeunadj lastupdated
date
2021-04-19 COST 2021-04-19 371.00 371.530 368.20 369.55 1560002.0 369.55 369.55 2021-04-19
2021-04-20 COST 2021-04-20 369.00 375.355 368.73 371.73 2330659.0 371.73 371.73 2021-04-20
2021-04-21 COST 2021-04-21 371.61 374.580 371.47 374.09 1532098.0 374.09 374.09 2021-04-21
2021-04-22 COST 2021-04-22 374.34 375.440 370.03 371.26 2138003.0 371.26 371.26 2021-04-22
2021-04-23 COST 2021-04-23 371.26 374.850 370.41 373.28 1404929.0 373.28 373.28 2021-04-23
# Set up the standard column name for modeling
# Column names may be data-provider specific!
MODEL_TEMPLATE = stock_pricing.loc[:, ['open','closeadj','volume']]
MODEL_TEMPLATE.rename(columns={'open': 'open_price', 'closeadj': 'close_price', 'volume': 'trading_volume'}, inplace=True)
plot_title = 'Historical Stock Close Price for ' + STOCK_SYMBOL + ' from ' + data_service
MODEL_TEMPLATE['close_price'].plot(figsize=(16,9), title=plot_title)
plt.show()
# Define the function that will generate the indicators and trading signals
# General logic for processing the trading signals for each time period
# 1 - Check to see whether we need to execute a trading action from the previous day's signal. Once the trading action is executed, move on to the next day.
# 2 - If no trade actions to execute on open, check to see whether we have any breakout that generates a trading signal. If we have a new trading signal and currently has no position, mark the entry action for the next day.
# 3 - If no new signal for today, check to see whether we need to exit any existing position. If we have an exit signal and currently hold a position, mark the exit action for the next day.
# 4 - If nothing is going on, mark up the trading model appropriately and move to the next day.
def populate_signals(window=WINDOW_MIN, vol_ma=VOL_MA_MIN, max_holding=HOLDING_MAX, RSI_size=RSI_SIZE):
trade_model = MODEL_TEMPLATE.copy()
trade_model['buy_on_open'] = False
trade_model['sell_on_open'] = False
trade_model['short_on_open'] = False
trade_model['cover_on_open'] = False
trade_model['holding_period'] = None
trade_model['cost_basis'] = None
trade_model['pandl_pct'] = None
trade_model['position_long'] = None
trade_model['position_short'] = None
trade_model['volume_ma'] = trade_model['trading_volume'].rolling(vol_ma).mean()
trade_model['rsi_value'] = 0
# Calculate the RSI ratings
gain_history = []
loss_history = []
for k in range(len(trade_model)):
close_price = trade_model.at[trade_model.index[k],'close_price']
if k == 0:
trade_model.at[trade_model.index[k],'rsi_value'] = 50
else:
previous_price = trade_model.at[trade_model.index[k-1],'close_price']
gain_history.append(max(0, close_price - previous_price))
loss_history.append(max(0, previous_price - close_price))
if len(gain_history) > window: # maximum observations is equal to lookback period
del (gain_history[0])
del (loss_history[0])
avg_gain = stats.mean(gain_history) # average gain over lookback period
avg_loss = stats.mean(loss_history) # average loss over lookback period
rs = 1
if avg_loss > 0: # to avoid division by 0, which is undefined
rs = avg_gain / avg_loss
trade_model.at[trade_model.index[k],'rsi_value'] = 100 - (100 / (1 + rs))
# Truncate the model to the required starting and ending dates
trade_model = trade_model[MODEL_START_DATE:MODEL_END_DATE]
last_index = len(trade_model) - 1
RSI_low_threshold = RSI_MIDDLE - RSI_size
RSI_high_threshold = RSI_MIDDLE + RSI_size
for k in range(len(trade_model)):
# Calculate the magnitude of breakout. Positive value means breaking out towards a direction
price_today = trade_model.at[trade_model.index[k],'close_price']
rsi_value = trade_model.at[trade_model.index[k],'rsi_value']
if MEAN_REVERSION:
breakout_long_side = rsi_value < RSI_low_threshold
breakout_short_side = rsi_value > RSI_high_threshold
else:
breakout_long_side = rsi_value > RSI_high_threshold
breakout_short_side = rsi_value < RSI_low_threshold
volume_today = trade_model.at[trade_model.index[k],'trading_volume']
volume_avg = trade_model.at[trade_model.index[k],'volume_ma']
close_above_threshold = rsi_value > RSI_high_threshold
close_below_threshold = rsi_value < RSI_low_threshold
if k == 0:
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0
else:
currently_long = trade_model.at[trade_model.index[k-1],'position_long']
currently_short = trade_model.at[trade_model.index[k-1],'position_short']
holding_period = trade_model.at[trade_model.index[k-1],'holding_period']
cost_basis = trade_model.at[trade_model.index[k-1],'cost_basis']
trade_executed_today = False
# Check to see whether we need to execute any trade action on open
if trade_model.at[trade_model.index[k],'buy_on_open']:
trade_executed_today = True
currently_long = True
currently_short = False
holding_period = 1
cost_basis = -trade_model.at[trade_model.index[k],'open_price']
elif trade_model.at[trade_model.index[k],'short_on_open']:
trade_executed_today = True
currently_long = False
currently_short = True
holding_period = 1
cost_basis = trade_model.at[trade_model.index[k],'open_price']
elif trade_model.at[trade_model.index[k],'sell_on_open']:
trade_executed_today = True
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0.0
elif trade_model.at[trade_model.index[k],'cover_on_open']:
trade_executed_today = True
currently_long = False
currently_short = False
holding_period = 0
cost_basis = 0.0
else:
# If no trade on open, check to see whether we have a breakout with an entry the next day
if breakout_long_side and (not currently_long) and (not currently_short) and (volume_today > volume_avg) and (k < last_index-1) and (not SHORT_ONLY):
trade_model.at[trade_model.index[k+1],'buy_on_open'] = True
elif breakout_short_side and (not currently_long) and (not currently_short) and (volume_today > volume_avg) and (k < last_index-1) and (not LONG_ONLY):
trade_model.at[trade_model.index[k+1],'short_on_open'] = True
else:
# If no breakout, check to see whether we need to exit an existing position the next day
if currently_short and (k < last_index-1):
if (MEAN_REVERSION and close_below_threshold) or (TREND_FOLLOWING and close_above_threshold) or (holding_period+1 >= max_holding):
trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
elif currently_long and (k < last_index-1):
if (MEAN_REVERSION and close_above_threshold) or (TREND_FOLLOWING and close_below_threshold) or (holding_period+1 >= max_holding):
trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
# If no action on a given day, carry over the position status
if (k > 0) and (not trade_executed_today):
if currently_long or currently_short:
holding_period = holding_period + 1
trade_model.at[trade_model.index[k],'position_long'] = currently_long
trade_model.at[trade_model.index[k],'position_short'] = currently_short
trade_model.at[trade_model.index[k],'holding_period'] = holding_period
trade_model.at[trade_model.index[k],'cost_basis'] = cost_basis
# Check to see whether the profit or loss target has been met for exiting the position
if currently_long :
pandl_pct = (cost_basis + price_today) / abs(cost_basis)
trade_model.at[trade_model.index[k],'pandl_pct'] = pandl_pct
if (pandl_pct >= GAIN_MAX) or (pandl_pct <= -LOSS_MAX) :
trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
elif currently_short :
pandl_pct = (cost_basis - price_today) / abs(cost_basis)
trade_model.at[trade_model.index[k],'pandl_pct'] = pandl_pct
if (pandl_pct >= GAIN_MAX) or (pandl_pct <= -LOSS_MAX) :
trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
else:
trade_model.at[trade_model.index[k],'pandl_pct'] = 0.0
# # Exiting the position on the last day of modeling period
# if k == last_index-1:
# if trade_model.at[trade_model.index[k],'position_long']:
# trade_model.at[trade_model.index[k+1],'sell_on_open'] = True
# trade_model.at[trade_model.index[k+1],'position_long'] = False
# elif trade_model.at[trade_model.index[k],'position_short']:
# trade_model.at[trade_model.index[k+1],'cover_on_open'] = True
# trade_model.at[trade_model.index[k+1],'position_short'] = False
if verbose_signals: print(trade_model, '\n')
return trade_model
# Build the collection of trading models by iterating through the parameters
trading_model_collection = {}
serial_no = 0
for window_size in range(WINDOW_MIN, WINDOW_MAX+1, WINDOW_INCREMENT):
for vol_average in range(VOL_MA_MIN, VOL_MA_MAX+1, VOL_MA_INCREMENT):
for hold_period in range(HOLDING_MIN, HOLDING_MAX+1, HOLDING_INCREMENT):
for rsi_size in range(10, 26, RSI_INCREMENT):
serial_no += 1
model_tag = 'Model_' + str(serial_no).zfill(3) + '_WINDOW_' + str(window_size).zfill(3) + '_VOLMA_' + str(vol_average).zfill(3) + '_HOLD_' + str(hold_period).zfill(3) \
+ '_RSI-HIGH_' + str(RSI_MIDDLE+rsi_size) + '_RSI-LOW_' + str(RSI_MIDDLE-rsi_size)
if verbose_signals: print('Processing model:', model_tag)
trading_model = populate_signals(window_size, vol_average, hold_period)
trading_model_collection[model_tag] = trading_model.copy()
print(len(trading_model_collection), 'trading models generated!')
36 trading models generated!
# List the entry/exit points for each model
def list_model_entry_exit(trade_model):
print(trade_model[trade_model['buy_on_open'] | trade_model['sell_on_open'] | trade_model['short_on_open'] | trade_model['cover_on_open']])
if verbose_models:
for model_name in trading_model_collection:
print('List the signal changes and entry/exit points for model:', model_name)
list_model_entry_exit(trading_model_collection[model_name])
print()
def draw_model_graph(trade_model, mdl_name=STOCK_SYMBOL):
graph_data = trade_model.copy()
title_string = 'Simple Mean-Reversion Trading Model for ' + mdl_name
fig = plt.figure(figsize=(16,18))
ylabel_1 = STOCK_SYMBOL + ' price in $'
ax1 = fig.add_subplot(211, ylabel=ylabel_1, title=title_string)
graph_data['close_price'].plot(ax=ax1, color='g')
ax1.plot(graph_data.loc[graph_data['buy_on_open']].index, graph_data.close_price[graph_data['buy_on_open']], '^', markersize=7, color='b',label='Buy on Open')
ax1.plot(graph_data.loc[graph_data['sell_on_open']].index, graph_data.close_price[graph_data['sell_on_open']], 'v', markersize=7, color='b',label='Sell on Open')
ax1.plot(graph_data.loc[graph_data['short_on_open']].index, graph_data.close_price[graph_data['short_on_open']], '^', markersize=7, color='r',label='Short on Open')
ax1.plot(graph_data.loc[graph_data['cover_on_open']].index, graph_data.close_price[graph_data['cover_on_open']], 'v', markersize=7, color='r',label='Cover on Open')
plt.legend(loc='upper left')
ylabel_2 = STOCK_SYMBOL + ' RSI'
ax2 = fig.add_subplot(212, ylabel=ylabel_2, title=title_string)
graph_data['rsi_value'].plot(ax=ax2, color='b')
plt.show()
if verbose_graphs:
for model_name in trading_model_collection:
draw_model_graph(trading_model_collection[model_name], model_name)
def generate_trading_portfolios(trade_model):
# Construct a portfolio to track the transactions and returns
portfolio = pd.DataFrame(index=trade_model.index, columns=['trade_action', 'price_executed', 'qty_transacted', 'cost_basis', 'gain_loss', 'qty_on_hand', 'cash_on_hand', 'position_value', 'total_position', 'accum_return'])
portfolio['trade_action'] = False
portfolio.at[portfolio.index[0],'price_executed'] = 0.00
portfolio.at[portfolio.index[0],'qty_transacted'] = 0
portfolio.at[portfolio.index[0],'cost_basis'] = 0.00
portfolio.at[portfolio.index[0],'gain_loss'] = 0.00
portfolio.at[portfolio.index[0],'qty_on_hand'] = 0
portfolio.at[portfolio.index[0],'cash_on_hand'] = INITIAL_CAPITAL
portfolio.at[portfolio.index[0],'position_value'] = 0.00
portfolio.at[portfolio.index[0],'total_position'] = INITIAL_CAPITAL
portfolio.at[portfolio.index[0],'accum_return'] = 0.00
quantity = 1
# The conditional parameters below determine how the trading strategy will be carried out
for i in range(1, len(portfolio)):
price_per_share = trade_model.at[trade_model.index[i],'open_price']
if trade_model.at[trade_model.index[i],'buy_on_open']:
# Code block for Buy on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = quantity
recent_cost = price_per_share * -quantity
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] + quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + recent_cost
if verbose_trade_actions: print('BOUGHT QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', price_per_share)
elif trade_model.at[trade_model.index[i],'sell_on_open']:
# Code block for Sell on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = -quantity
recent_cost = 0.00
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = (price_per_share * quantity) + portfolio.iloc[i-1]['cost_basis']
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] - quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + (price_per_share * quantity)
if verbose_trade_actions: print('SOLD QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', price_per_share)
elif trade_model.at[trade_model.index[i],'short_on_open']:
# Code block for Short on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = -quantity
recent_cost = price_per_share * quantity
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] - quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] + recent_cost
if verbose_trade_actions: print('SHORTED QTY:', -quantity, 'on', portfolio.index[i].date(), 'at the price of', trade_model.at[portfolio.index[i],'open_price'])
elif trade_model.at[trade_model.index[i],'cover_on_open']:
# Code block for Cover on Open
portfolio.at[portfolio.index[i],'trade_action'] = True
portfolio.at[portfolio.index[i],'price_executed'] = price_per_share
portfolio.at[portfolio.index[i],'qty_transacted'] = quantity
recent_cost = 0.00
portfolio.at[portfolio.index[i],'cost_basis'] = recent_cost
portfolio.at[portfolio.index[i],'gain_loss'] = portfolio.iloc[i-1]['cost_basis'] - (price_per_share * quantity)
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand'] + quantity
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand'] - (price_per_share * quantity)
if verbose_trade_actions: print('COVERED QTY:', quantity, 'on', portfolio.index[i].date(), 'at the price of', trade_model.at[portfolio.index[i],'open_price'])
else:
# Code block for no trade actions
portfolio.at[portfolio.index[i],'price_executed'] = 0.00
portfolio.at[portfolio.index[i],'qty_transacted'] = 0
portfolio.at[portfolio.index[i],'cost_basis'] = portfolio.iloc[i-1]['cost_basis']
portfolio.at[portfolio.index[i],'gain_loss'] = 0.00
portfolio.at[portfolio.index[i],'qty_on_hand'] = portfolio.iloc[i-1]['qty_on_hand']
portfolio.at[portfolio.index[i],'cash_on_hand'] = portfolio.iloc[i-1]['cash_on_hand']
portfolio.at[portfolio.index[i],'position_value'] = trade_model.at[trade_model.index[i],'close_price'] * portfolio.at[portfolio.index[i],'qty_on_hand']
portfolio.at[portfolio.index[i],'total_position'] = portfolio.at[portfolio.index[i],'cash_on_hand'] + portfolio.at[portfolio.index[i],'position_value']
portfolio.at[portfolio.index[i],'accum_return'] = portfolio.at[portfolio.index[i],'total_position'] - INITIAL_CAPITAL
if verbose_portfolios: print('\n', portfolio, '\n')
return portfolio
def calculate_positions_and_performance(trade_model):
trade_positions = generate_trading_portfolios(trade_model)
trade_transactions = trade_positions[trade_positions['trade_action']]
if verbose_transactions: print(trade_transactions)
if trade_transactions.at[trade_transactions.index[-1],'trade_action']:
if trade_transactions.at[trade_transactions.index[-1],'qty_on_hand'] == 0:
print('The current status of the model is:','Waiting to enter a position since',trade_transactions.index.tolist()[-1].date(),'\n')
elif trade_transactions.at[trade_transactions.index[-1],'qty_on_hand'] > 0:
print('The current status of the model is:','Holding a long position since',trade_transactions.index.tolist()[-1].date(),'\n')
else:
print('The current status of the model is:','Holding a short position since',trade_transactions.index.tolist()[-1].date(),'\n')
return trade_positions
# Convert trading models into positions and calculate profit and loss
# Initialize a dictionary for tracking positions for all models
model_positions_collection={}
for model_name in trading_model_collection:
print('Processing the positions for model:', model_name)
model_positions_collection[model_name] = calculate_positions_and_performance(trading_model_collection[model_name])
print(len(model_positions_collection), 'sets of model positions generated.')
Processing the positions for model: Model_001_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_002_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_003_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_004_WINDOW_010_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_005_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_006_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_007_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_008_WINDOW_015_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_009_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_010_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_011_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_012_WINDOW_020_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a short position since 2021-04-09 Processing the positions for model: Model_013_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a short position since 2021-04-19 Processing the positions for model: Model_014_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a short position since 2021-04-19 Processing the positions for model: Model_015_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a short position since 2021-04-19 Processing the positions for model: Model_016_WINDOW_025_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a short position since 2021-04-19 Processing the positions for model: Model_017_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a short position since 2021-04-21 Processing the positions for model: Model_018_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a short position since 2021-04-21 Processing the positions for model: Model_019_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a short position since 2021-04-21 Processing the positions for model: Model_020_WINDOW_030_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a short position since 2021-04-21 Processing the positions for model: Model_021_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_022_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_023_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_024_WINDOW_035_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-05 Processing the positions for model: Model_025_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_026_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_027_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_028_WINDOW_040_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-04 Processing the positions for model: Model_029_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_030_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_031_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_032_WINDOW_045_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Holding a long position since 2021-03-09 Processing the positions for model: Model_033_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_60_RSI-LOW_40 The current status of the model is: Waiting to enter a position since 2020-10-08 Processing the positions for model: Model_034_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_65_RSI-LOW_35 The current status of the model is: Waiting to enter a position since 2020-10-08 Processing the positions for model: Model_035_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_70_RSI-LOW_30 The current status of the model is: Waiting to enter a position since 2020-10-08 Processing the positions for model: Model_036_WINDOW_050_VOLMA_010_HOLD_999_RSI-HIGH_75_RSI-LOW_25 The current status of the model is: Waiting to enter a position since 2020-10-08 36 sets of model positions generated.
# Initialize a dataframe for storing the model's profit and loss
model_performance_summary = pd.DataFrame(columns=['Model_name','Return_value','Return_percentage'])
for model_name in model_positions_collection:
if verbose_positions: print('Processing positions for model:', model_name)
if verbose_positions: print('Accumulated profit/loss for one share of stock with initial capital of $%.0f at the end of modeling period: $%.2f' % (INITIAL_CAPITAL, model_positions_collection[model_name].accum_return[-1]))
if INITIAL_CAPITAL != 0:
return_percentage = model_positions_collection[model_name].accum_return[-1] / INITIAL_CAPITAL * 100
if verbose_positions: print('Accumulated return percentage based on the initial capital investment: %.2f%%' % return_percentage)
else:
return_percentage = None
if verbose_positions: print()
model_performance_summary = model_performance_summary.append({'Model_name': model_name, 'Return_value': model_positions_collection[model_name].accum_return[-1], 'Return_percentage': return_percentage}, ignore_index=True)
model_performance_summary.sort_values(by=['Return_value'], inplace=True, ascending=False)
print(len(model_performance_summary), 'profit/loss summaries generated.\n')
print('The top ten model\'s performance summary:')
print(model_performance_summary.head(10))
36 profit/loss summaries generated.
The top ten model's performance summary:
Model_name Return_value Return_percentage
0 Model_001_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 51.190 None
2 Model_002_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 51.190 None
3 Model_003_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 51.190 None
1 Model_004_WINDOW_010_VOLMA_010_HOLD_999_RSI-HI... 51.190 None
4 Model_018_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 1.911 None
5 Model_020_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 1.911 None
6 Model_019_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 1.911 None
7 Model_017_WINDOW_030_VOLMA_010_HOLD_999_RSI-HI... 1.911 None
10 Model_016_WINDOW_025_VOLMA_010_HOLD_999_RSI-HI... -49.460 None
11 Model_014_WINDOW_025_VOLMA_010_HOLD_999_RSI-HI... -49.460 None
# Calculate the stock's performance for a buy-and-hold model
top_model_name = model_performance_summary.loc[0]['Model_name']
top_trading_model = trading_model_collection[top_model_name]
print('The entry point for the buy-and-hold model: $%.2f on %s' % (top_trading_model.iloc[0]['open_price'], top_trading_model.index[0].date()))
print('The exit point for the buy-and-hold model: $%.2f on %s' % (top_trading_model.iloc[-1]['open_price'], top_trading_model.index[-1].date()))
print('The performance of the buy-and-hold model: $%.2f' %(top_trading_model.iloc[-1]['open_price'] - top_trading_model.iloc[0]['open_price']))
print('The performance of the top trading model: $%.2f' %(model_performance_summary.iloc[0]['Return_value']))
The entry point for the buy-and-hold model: $159.81 on 2016-01-04 The exit point for the buy-and-hold model: $371.26 on 2021-04-23 The performance of the buy-and-hold model: $211.45 The performance of the top trading model: $51.19
top_model_positions = model_positions_collection[top_model_name]
print(top_model_positions[top_model_positions['trade_action'] != 0])
trade_action price_executed qty_transacted cost_basis gain_loss qty_on_hand cash_on_hand position_value total_position accum_return date 2016-01-08 True 155.28 1 -155.28 0 1 -155.28 135.069 -20.211 -20.211 2016-02-22 True 149.63 -1 0 -5.65 0 -5.65 0 -5.65 -5.65 2016-02-25 True 154.69 -1 154.69 0 -1 149.04 -137.758 11.282 11.282 2016-04-14 True 152.59 1 0 2.1 0 -3.55 0 -3.55 -3.55 2016-05-02 True 148.76 1 -148.76 0 1 -152.31 134.782 -17.528 -17.528 2016-06-02 True 150.05 -1 0 1.29 0 -2.26 0 -2.26 -2.26 2016-06-13 True 154.76 -1 154.76 0 -1 152.5 -138.372 14.128 14.128 2016-08-26 True 165.39 1 0 -10.63 0 -12.89 0 -12.89 -12.89 2016-08-30 True 164.07 1 -164.07 0 1 -176.96 145.019 -31.941 -31.941 2016-11-18 True 150.84 -1 0 -13.23 0 -26.12 0 -26.12 -26.12 2016-12-09 True 157.28 -1 157.28 0 -1 131.16 -143.435 -12.275 -12.275 2017-01-05 True 160.95 1 0 -3.67 0 -29.79 0 -29.79 -29.79 2017-01-18 True 163.65 -1 163.65 0 -1 133.86 -147.539 -13.679 -13.679 2017-03-06 True 169.41 1 0 -5.76 0 -35.55 0 -35.55 -35.55 2017-04-11 True 170.79 -1 170.79 0 -1 135.24 -153.661 -18.421 -18.421 2017-05-19 True 171.35 1 0 -0.56 0 -36.11 0 -36.11 -36.11 2017-05-30 True 177.68 -1 177.68 0 -1 141.57 -168.149 -26.579 -26.579 2017-06-19 True 167.05 1 0 10.63 0 -25.48 0 -25.48 -25.48 2017-06-21 True 163.49 1 -163.49 0 1 -188.97 153.328 -35.642 -35.642 2017-08-01 True 159.1 -1 0 -4.39 0 -29.87 0 -29.87 -29.87 2017-08-03 True 161.61 -1 161.61 0 -1 131.74 -148.404 -16.664 -16.664 2018-01-03 True 188.82 1 0 -27.21 0 -57.08 0 -57.08 -57.08 2018-01-23 True 193.61 -1 193.61 0 -1 136.53 -182.878 -46.348 -46.348 2018-02-06 True 178.91 1 0 14.7 0 -42.38 0 -42.38 -42.38 2018-02-09 True 179.78 1 -179.78 0 1 -222.16 170.87 -51.29 -51.29 2018-02-26 True 190.1 -1 0 10.32 0 -32.06 0 -32.06 -32.06 2018-04-17 True 195.66 -1 195.66 0 -1 163.6 -185.802 -22.202 -22.202 2018-09-25 True 234.03 1 0 -38.37 0 -70.43 0 -70.43 -70.43 2018-10-08 True 219.34 1 -219.34 0 1 -289.77 213.423 -76.347 -76.347 2018-10-22 True 229.88 -1 0 10.54 0 -59.89 0 -59.89 -59.89 2018-11-21 True 220.56 1 -220.56 0 1 -280.45 208.812 -71.638 -71.638 2018-12-07 True 233.15 -1 0 12.59 0 -47.3 0 -47.3 -47.3 2018-12-17 True 206.68 1 -206.68 0 1 -253.98 193.202 -60.778 -60.778 2019-01-09 True 209.15 -1 0 2.47 0 -44.83 0 -44.83 -44.83 2019-01-11 True 210.04 -1 210.04 0 -1 165.21 -201.112 -35.902 -35.902 2019-06-03 True 239.78 1 0 -29.74 0 -74.57 0 -74.57 -74.57 2019-06-24 True 267.31 -1 267.31 0 -1 192.74 -256.237 -63.497 -63.497 2019-08-01 True 275.75 1 0 -8.44 0 -83.01 0 -83.01 -83.01 2019-08-05 True 270.09 1 -270.09 0 1 -353.1 254.633 -98.467 -98.467 2019-08-29 True 297.48 -1 0 27.39 0 -55.62 0 -55.62 -55.62 2019-09-09 True 304.5 -1 304.5 0 -1 248.88 -287.824 -38.944 -38.944 2019-09-23 True 286.36 1 0 18.14 0 -37.48 0 -37.48 -37.48 2019-10-02 True 283.23 1 -283.23 0 1 -320.71 274.454 -46.256 -46.256 2019-10-14 True 298 -1 0 14.77 0 -22.71 0 -22.71 -22.71 2019-10-23 True 299.58 -1 299.58 0 -1 276.87 -286.207 -9.337 -9.337 2019-12-02 True 299.75 1 0 -0.17 0 -22.88 0 -22.88 -22.88 2019-12-04 True 296.04 1 -296.04 0 1 -318.92 286.062 -32.858 -32.858 2020-01-17 True 303.4 -1 0 7.36 0 -15.52 0 -15.52 -15.52 2020-01-22 True 313.4 -1 313.4 0 -1 297.88 -300.889 -3.009 -3.009 2020-02-05 True 307 1 0 6.4 0 -9.12 0 -9.12 -9.12 2020-02-24 True 315.5 -1 315.5 0 -1 306.38 -303.192 3.188 3.188 2020-02-28 True 285.25 1 0 30.25 0 21.13 0 21.13 21.13 2020-06-01 True 307.9 -1 307.9 0 -1 329.03 -298.727 30.303 30.303 2020-06-15 True 297.05 1 0 10.85 0 31.98 0 31.98 31.98 2020-06-22 True 299.7 1 -299.7 0 1 -267.72 291.13 23.41 23.41 2020-07-07 True 310.02 -1 0 10.32 0 42.3 0 42.3 42.3 2020-07-10 True 326 -1 326 0 -1 368.3 -316.111 52.189 52.189 2020-09-18 True 340.2 1 0 -14.2 0 28.1 0 28.1 28.1 2020-10-13 True 378.42 -1 378.42 0 -1 406.52 -369.667 36.853 36.853 2020-10-29 True 363.23 1 0 15.19 0 43.29 0 43.29 43.29 2020-11-02 True 362.22 1 -362.22 0 1 -318.93 354.527 35.597 35.597 2020-11-24 True 383.69 -1 0 21.47 0 64.76 0 64.76 64.76 2020-11-27 True 390.46 -1 390.46 0 -1 455.22 -377.877 77.343 77.343 2020-12-16 True 371.16 1 0 19.3 0 84.06 0 84.06 84.06 2021-01-20 True 354.39 1 -354.39 0 1 -270.33 360.591 90.261 90.261 2021-03-23 True 333.89 -1 0 -20.5 0 63.56 0 63.56 63.56 2021-04-09 True 360.91 -1 360.91 0 -1 424.47 -363.21 61.26 61.26
draw_model_graph(trading_model_collection[top_model_name], top_model_name)
print ('Total time for the script:',(datetime.now() - startTimeScript))
Total time for the script: 0:03:59.883488